﻿using System;
using System.Linq;
using System.Reflection;
using CoreEx.Database.Metadata;
using CoreEx.MicroMapper;
using CoreEx.MicroMapper.Implementation;
using LinFu.IoC.Interfaces;
using LinFu.IoC;
using CoreEx.Common.Extensions;

namespace CoreEx.Database.MetaData.Implementation
{
    
    public class TypeInfoMapper_old<TSource> : MicroMapper<Type,ITypeMappingInfo>, ITypeInfoMapper_old<TSource>
    {
        private readonly string _connectionName;
        private IMetadataRepository _metadataRepository;

        public TypeInfoMapper_old(string connectionName)
        {
            _connectionName = connectionName;
        }

        public virtual TableInfo GetTableInfo(Type type)
        {
            //Assume that interfaces has the "I" prefix
            var typeName = type.IsInterface ? type.Name.Substring(1) : type.Name;
            
            //Find all tables that contains the type name
            //var targetTables = _metadataRepository.Tables.Where(t => t.TableName.Replace(" ","").Contains(typeName));
            
            //No tables found.
            //if (targetTables.Count() == 0)
            //{
                var tableName = typeName.GetBestMatch(_metadataRepository.Tables.Select(t => t.TableName));
                var tableInfo = _metadataRepository.Tables.Where(t => t.TableName == tableName).FirstOrDefault();
                return tableInfo;
            //}                



            //Return the best match
            //return targetTables.OrderBy(t => t.TableName.Replace(typeName, "").Length).First();
        }

        public virtual ColumnInfo GetColumnInfo(PropertyInfo propertyInfo, TableInfo tableInfo)
        {
            if (tableInfo == null)
                return null;            
            //Find all tables that contains the type name
            var targetColumns = tableInfo.Columns.Values.Where(t => t.ColumnName.ToLower().Contains(propertyInfo.Name.ToLower()));

            //No columns found
            if (targetColumns.Count() == 0)
                return null;

            //Return the best match.
            return targetColumns.OrderBy(t => t.ColumnName.ToLower().Replace(propertyInfo.Name.ToLower(), "").Length).First();            
        }

        public virtual ForeignKeyInfo GetForeignKeyInfo(PropertyInfo propertyInfo, TableInfo tableInfo)
        {            

            
            if (!propertyInfo.PropertyType.IsEnumerable())
            {

                //Note improve this by evaluating plausible foreign keys 
                var targetColumns =
                    tableInfo.ForeignKeys.SelectMany(fk => fk.Columns);              
                
                var columnName = propertyInfo.Name.GetBestMatch(targetColumns.Select(c => c.ForeignColumnInfo.ColumnName));

                var column = targetColumns.Where(c => c.ForeignColumnInfo.ColumnName == columnName).FirstOrDefault();                                    

                return column.ForeignKey;
                 
            }
            var elementType = propertyInfo.PropertyType.GetGenericElementType();
            var foreigntableInfo = GetTableInfo(elementType);

            //Find the foreign key that refers to the target table
            var foreignKey = foreigntableInfo.ForeignKeys.Where(fk => fk.PrimaryTable == tableInfo).FirstOrDefault();

            
            return foreignKey;           
        }


        


        protected virtual string StripKeyName(string keyColumnName)
        {
            if (keyColumnName.ToLower().EndsWith("no") || keyColumnName.ToLower().EndsWith("id") || keyColumnName.ToLower().EndsWith("nr"))
                return keyColumnName.Substring(0, keyColumnName.Length - 2);
            if (keyColumnName.ToLower().EndsWith("guid"))
                return keyColumnName.Substring(0, keyColumnName.Length - 4);
            return keyColumnName;
        }

        public override void Initialize(IServiceContainer source)
        {
            base.Initialize(source);
            
            //Only explicit mappings for this mapper
            SourceAccessorSelector = null;
            TargetAccessorSelector = null;
            
            //Get a reference to the metadata repository
            _metadataRepository = source.GetService<IMetadataRepository>(_connectionName);

            //Set up the target factory
            TargetFactory = sourceType => new TypeMappingInfo();
            
            //Map the type itself
            Explicit((sourceType, tableMappingInfo) => tableMappingInfo.Type = typeof (TSource));
            
            //Find the table info that matches the source type
            Explicit(
                (sourceType, tableMappingInfo) =>
                tableMappingInfo.TableInfo = GetTableInfo(typeof(TSource)));
            
            //Set up the column mapping
            Explicit(
                (sourceType, tableMappingInfo) =>
                    {
                        var propertyMapper = source.GetService<IMicroMapper<PropertyInfo, IPropertyMappingInfo>>();
                        propertyMapper.Explicit(
                            (propertyInfo, columnMappingInfo) => columnMappingInfo.Property = propertyInfo);
                        propertyMapper.Explicit(
                            (propertyInfo, columnMappingInfo) => columnMappingInfo.ColumnInfo =
                                                                 GetColumnInfo(propertyInfo, tableMappingInfo.TableInfo));
                        //Map all non-relational properties
                        propertyMapper.Apply(typeof (TSource).GetProperties().Where(t => t.PropertyType.IsSystemType() && !t.PropertyType.IsEnumerable())).ForEach(
                            c => tableMappingInfo.ColumnMappings.Add(c.Property, c));

                       
                        propertyMapper.Reset();

                        propertyMapper.Explicit(
                            (propertyInfo, columnMappingInfo) => columnMappingInfo.Property = propertyInfo);

                        propertyMapper.Explicit(
                            (propertyInfo, propertyMappingInfo) =>
                                {
                                    
                                    
                                    var foreignKeyInfo = GetForeignKeyInfo(propertyInfo, tableMappingInfo.TableInfo);

                                    if (foreignKeyInfo == null && propertyInfo.PropertyType.IsEnumerable())
                                    {
                                        var foreignTableInfo =
                                            GetTableInfo(propertyInfo.PropertyType.GetGenericElementType());

                                        var junctionTableCandidates = tableMappingInfo.TableInfo.Relations
                                            .Where(r => r.PrimaryKey != tableMappingInfo.TableInfo.PrimaryKey).Select(r => r.PrimaryKey.Table);

                                        var juctionTable = junctionTableCandidates.SelectMany(t => t.ForeignKeys).Where(
                                            f => f.PrimaryTable == foreignTableInfo).Select(fk => fk.Table).FirstOrDefault();

                                        var foreignKey =
                                            juctionTable.ForeignKeys.Where(
                                                fk => fk.PrimaryTable == tableMappingInfo.TableInfo).FirstOrDefault();

                                        propertyMappingInfo.ForeignKeyInfo = foreignKey;
                                        propertyMappingInfo.RelationType = CardinalityType.ManyToMany;
                                    }

                                    else
                                    {
                                        propertyMappingInfo.ForeignKeyInfo = foreignKeyInfo; 
                                        propertyMappingInfo.RelationType =
                                            propertyInfo.PropertyType.IsEnumerable()
                                                ? CardinalityType.OneToMany
                                                : CardinalityType.ManyToOne;
                                    }

                                });

                        
                        //Map all relations
                        propertyMapper.Apply(typeof(TSource).GetProperties().Where(t => !t.PropertyType.IsSystemType() )).ForEach(
                            c => tableMappingInfo.ColumnMappings.Add(c.Property, c));


                       
                    });            
                                    
        }


        private void MapRelations(PropertyInfo propertyInfo, IPropertyMappingInfo propertyMappingInfo)
        {
                            
        }

    }
}
